#!/usr/bin/env python

# Greg Johnston
#  <greg.johnston@gmail.com>
#  ritual.py
#  The main executable/GUI file

import sys, os
import gtk
import gtk.glade

from log import RitualLog
from coolrunning import CoolRunningLog
from config import RitualConfig
from calc import *

class RitualGTK:
	"""This is the GTK GUI controller for Ritual"""

	Log = RitualLog()
	ViewerOffset = 0 # which run to start on
	EditorMode = 0 # normally, we want to add, not edit
	EditorShoesSoFar = []
	EditorCoursesSoFar = []

	def __init__(self):
		# Load config
		self.Config = RitualConfig()
		self.CurrentFile = self.Config.config["default_file"]

		# Load GUI
		self.gladefile = "ritual.glade"
		self.wTree = gtk.glade.XML(self.gladefile)

		# Set images
		self.wTree.get_widget("top").set_icon_from_file(self.Config.config["logo"])
		self.wTree.get_widget("editor").set_icon_from_file(self.Config.config["logo"])
		self.wTree.get_widget("about").set_icon_from_file(self.Config.config["logo"])
		self.wTree.get_widget("about").set_logo(self.wTree.get_widget("about").get_icon())
		self.wTree.get_widget("calculator").set_icon_from_file(self.Config.config["logo"])

		# Set up main window
		self.window = self.wTree.get_widget("top")
		if (self.window):
			self.window.connect("destroy", quit)
			self.set_handlers()
		self.Calendar = self.wTree.get_widget("calendar1")
		self.title()

		# Set up Calendar
		from datetime import date
		self.calendar_to_month_year(date.today())
		self.Calendar.year = date.today().year

		# Set up Log
		try:
			self.Log.read_file(self.Config.config["default_file"])
		except IOError:
			pass

		# Mark calendar, show curve
		self.calendar_mark_month()
		self.show_curve()

	def set_handlers(self):
		"""Connects signals with handlers"""
		dic = { "on_imagemenuitem1_activate" : self.new,
				"on_imagemenuitem2_activate" : self.convert,
				"on_imagemenuitem3_activate" : self.open,
				"on_imagemenuitem4_activate" : self.save,
				"on_imagemenuitem5_activate" : self.save_as,
				"on_imagemenuitem6_activate" : self.quit,
				"on_imagemenuitem11_activate" : self.show_about,
				"on_calendar1_day_selected_double_click" : self.show_editor,
				"on_calendar1_day_selected" : self.show_run_data,
				"on_editor_close" : self.hide_editor,
				"on_editorcalc_clicked" : self.time_calc,
				"on_calc_add" : self.calc_add,
				"on_courseentry_changed" : self.courseentry_changed,
				"on_editor_submit" : self.submit,
				"on_date_today" : self.date_today,
				"on_update_pace" : self.update_pace,
				"remark_calendar" : self.calendar_change,
				"on_previous_clicked" : self.reader_backward,
				"on_next_clicked" : self.reader_forward,
				"on_edit_clicked" : self.edit_run,
				"on_delete_clicked" : self.delete_run }
		self.wTree.signal_autoconnect(dic)

	def title(self):
		"""Sets the top window's title to indicate the file"""
		if self.CurrentFile and self.CurrentFile != "":
			t = os.path.split(self.CurrentFile)[1] + " - Ritual"
		else:
			t = "Ritual"
		self.wTree.get_widget("top").set_title(t)

	def quit(self, error):
		"""Quit the program properly"""
		gtk.main_quit()
		sys.exit(1)

	def show_about(self, widget):
		"""Shows the AboutDialog widget"""
		self.wTree.get_widget("about").run()
		self.wTree.get_widget("about").hide()

	def hide_about(self, widget):
		"""Hides the AboutDialog widget"""
		self.wTree.get_widget("about").hide()

	## Calendar controllers
	def calendar_change(self, signal):
		"""Clears the marked days on the calendar. Remarks the days which are in this month."""
		self.Calendar.clear_marks()
		self.calendar_mark_month()

	def calendar_to_month_year(self, date):
		"""Moves the calendar to the month and year it is passed"""
		self.Calendar.year = date.year
		self.Calendar.month = date.month
		self.calendar_mark_month()

	def calendar_mark_month(self):
		"""Marks all the days in the selected month which have runs"""
		self.Calendar.clear_marks()
		chosen_month = self.Calendar.get_date()[1]+1 # returns month as 0-11
		for date_tuple in self.Log.log.keys():
			date = date_tuple[0]
			if date.month == chosen_month:
				self.Calendar.mark_day(date.day)

	def calendar_selected_date(self):
		"""Returns the date selected by the calendar as a MM/DD/YYYY string"""
		tuple = self.wTree.get_widget("calendar1").get_date()
		return str(tuple[1]+1) + "/" + str(tuple[2]) + "/" + str(tuple[0]) # +1 is: for some reason gives month starting at 0

	## Editor controllers
	def set_courses_combo(self):
		"""Sets up the courseentry ComboBoxEntry"""
		combobox = self.wTree.get_widget("courseentry")
		combobox.remove_text(0)
		for course in self.Log.courses:
			if not course in self.EditorCoursesSoFar:
				self.EditorCoursesSoFar += [course]
				combobox.append_text(course)

	def set_shoes_combo(self):
		"""Sets up the shoesentry ComboBoxEntry"""
		combobox = self.wTree.get_widget("shoesentry")
		combobox.remove_text(0)
		for date in self.Log.log:
			entry = self.Log.log[date]
			if not entry.shoes in self.EditorShoesSoFar:
				self.EditorShoesSoFar += [entry.shoes]
				combobox.append_text(entry.shoes)

	def show_editor(self, widget):
		"""Shows the Editor window"""
		self.EditorMode = 0 # Add
		self.set_courses_combo()
		self.set_shoes_combo()	
		self.wTree.get_widget("dateentry").set_text(self.calendar_selected_date())
		self.wTree.get_widget("courseentry-entry").set_text("")
		self.wTree.get_widget("distanceentry").set_text("")
		self.wTree.get_widget("distanceunit").set_active(0)
		self.wTree.get_widget("timeentry").set_text("HH:MM:SS")
		self.wTree.get_widget("pace").set_text("")
		self.wTree.get_widget("notesentry").set_text("")
		self.wTree.get_widget("editor").show()

	def edit_run(self, widget):
		"""Edits the currently-selected run"""
		self.EditorMode = 1 # Edit
		selected = self.Calendar.get_date()
		sdate = date(selected[0], selected[1]+1, selected[2])  #(year, month+1, day)
		run = self.Log.log[(sdate, self.ViewerOffset)]
		self.set_courses_combo()
		self.set_shoes_combo()	
		self.wTree.get_widget("dateentry").set_text(self.calendar_selected_date())
		self.wTree.get_widget("courseentry-entry").set_text(run.course)
		self.wTree.get_widget("distanceentry").set_text(str(run.distance))
		self.wTree.get_widget("distanceunit").set_active(run.unit)
		self.wTree.get_widget("timeentry").set_text(seconds_timestr(run.time))
		self.wTree.get_widget("shoesentry-entry").set_text(run.shoes)
		self.wTree.get_widget("pace").set_text("")
		self.wTree.get_widget("notesentry").set_text(run.notes)
		self.wTree.get_widget("editor").show()

	def delete_run(self, widget):
		selected = self.Calendar.get_date()
		sdate = date(selected[0], selected[1]+1, selected[2])  #(year, month+1, day)
		self.Log.remove_entry(sdate, self.ViewerOffset)
		self.save(None)
		self.show_run_data(self.Calendar)

	def hide_editor(self, widget):
		"""Hides the Editor window"""
		self.wTree.get_widget("editor").hide()

	def date_today(self, widget):
		"""Sets the value of the date field to today's date"""
		return self.wTree.get_widget("dateentry").set_text(date.today().strftime("%m/%d/%Y"))

	def submit(self, widget):
		"""Submits the form values in the editor to a log entry"""
		date = string_date(self.wTree.get_widget("dateentry").get_text())
		offset = self.ViewerOffset
		if self.EditorMode == 1: # Edit Mode
			self.Log.remove_entry(date, offset)
		self.Log.add_entry(date,
							escape(self.wTree.get_widget("courseentry-entry").get_text()),
							float(self.wTree.get_widget("distanceentry").get_text()),
							self.wTree.get_widget("distanceunit").get_active(),
							time_in_seconds(self.wTree.get_widget("timeentry").get_text()),
							escape(self.wTree.get_widget("shoesentry-entry").get_text()),
							escape(self.wTree.get_widget("notesentry").get_text()),
							offset=offset)
		self.save(None)
		self.wTree.get_widget("editor").hide()
		self.calendar_mark_month()
		self.show_curve()
		self.show_run_data(self.Calendar)

	def update_pace(self, widget, sy):
		"""Updates the pace label value to read the pace, based on distance/time"""
		tstr = self.wTree.get_widget("timeentry").get_text()
		unit = self.wTree.get_widget("distanceunit").get_active()
		try:
			distance = float(self.wTree.get_widget("distanceentry").get_text())
			return self.wTree.get_widget("pacevalue").set_text(calc_pace(tstr, distance, unit))
		except ValueError:
			return 0

	def courseentry_changed(self, widget):
		"""Updates the distance and unit based on the courseentry"""
		course = self.wTree.get_widget("courseentry-entry").get_text()
		if course in self.Log.courses:
			self.wTree.get_widget("distanceentry").set_text(self.Log.courses[course][0])
			self.wTree.get_widget("distanceunit").set_active(self.Log.courses[course][1])

	def time_calc(self, widget):
		"""Sets the timeentry appropriately after popping up a calculator"""
		dialog = self.wTree.get_widget("calculator")
		response = dialog.run()
		times = []
		if response == 1: # OK
			for hbox in self.wTree.get_widget("dialog-vbox4").get_children():
				for child in hbox.get_children():
					try:
						times += [child.get_text()]
					except:
						pass
		elif response == 0: # Cancel
			return "HH:MM:SS"
		final = add_times(times)
		self.wTree.get_widget("timeentry").set_text(final)
		dialog.hide()

	def calc_add(self, button):
		"""Adds a row to the calculator"""
		vbox = button.get_parent().get_parent()
		hbox = gtk.HBox()
		vbox.add(hbox)
		entry = gtk.Entry()
		entry.grab_focus()
		hbox.add(entry)
		hbox.show()
		entry.show()

	## Curve functions
	def mpw_curve_points(self):
		"""Generates a list of points for the Curve widget to display"""
		vector = range(1, 53)
		for week in range(1, 53):
			vector[week-1] = self.Log.miles_in_week(week, self.Calendar.year)
		return vector

	def show_curve(self):
		"""Shows the Curve widget, updated"""
		self.wTree.get_widget("curve1").set_vector(self.mpw_curve_points())

	## Convert/Import/IO Functions
	def fileprompt(self, title, okaybutton, filter, action):
		"""Returns a filename, given a title, filter, and GTK stock button"""
		dialog = gtk.FileChooserDialog(title=title,action=action,
										buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
												 okaybutton,gtk.RESPONSE_OK))
		dialog.set_default_response(gtk.RESPONSE_OK)
		dialog.add_filter(filter)
		dialog.set_current_folder(os.path.expanduser("~"))

		response = dialog.run()
		if response == gtk.RESPONSE_OK:
			filename = dialog.get_filename()
		elif response == gtk.RESPONSE_CANCEL:
			filename = True
		dialog.destroy()
		return filename

	def new(self, widget):
		"""Saves current log and creates a new one"""
		self.save(widget)
		self.CurrentFile = None
		self.log = RitualLog()
		self.Config.config["default_file"] = None
		self.Config.save_config()
		self.calendar_mark_month()

	def open(self, widget):
		"""Selects a file to open and opens it"""
		filter = gtk.FileFilter()
		filter.set_name("Ritual files (*.ritual)")
		filter.add_pattern("*.ritual")
		filename = self.fileprompt("Open...", gtk.STOCK_OPEN, filter, gtk.FILE_CHOOSER_ACTION_OPEN)
		self.open_file(filename)

	def open_file(self, filename):
		"""Opens a file, given its name"""
		self.Log.read_file(filename)
		self.Config.config["default_file"] = filename
		self.Config.save_config()
		self.CurrentFile = filename
		self.calendar_mark_month()
		self.show_curve()
		self.title()

	def convert(self, widget):
		"""Imports a Coolrunning-formatted log"""
		filter = gtk.FileFilter()
		filter.set_name("Coolrunning (downloadlog.cgi, *.csv)")
		filter.add_pattern("downloadlog.cgi")
		filter.add_pattern("*.csv")
		filename = self.fileprompt("Convert...", gtk.STOCK_CONVERT, filter, gtk.FILE_CHOOSER_ACTION_OPEN)
		crlog = CoolRunningLog(filename)
		merged = merge_logs(self.Log, crlog)
		self.Log.courses = merged[0]
		self.Log.log = merged[1]
		self.save(widget)

	def save_as(self, widget):
		"""Saves the currently open file to a new file"""
		filter = gtk.FileFilter()
		filter.set_name("Ritual files (*.ritual)")
		filter.add_pattern("*.ritual")
		filename = self.fileprompt("Save as...", gtk.STOCK_SAVE, filter, gtk.FILE_CHOOSER_ACTION_SAVE)
		self.CurrentFile = filename
		self.save(widget)
		self.title()

	def save(self, widget):
		"""Saves the currently open file"""
		if self.CurrentFile and self.CurrentFile != "":
			self.Log.save_file(self.CurrentFile)
		else:
			self.save_as(widget)

	## Viewer controllers

	def day_selected(self, calendar):
		"""Updates the right-hand viewer to the first run for the selected day"""
		self.ViewerOffset = 0
		self.show_run_data(run)

	def reader_backward(self, button):
		"""Moves the reader back one run"""
		self.ViewerOffset -= 1
		self.show_run_data(self.Calendar)

	def reader_forward(self, button):
		"""Moves the reader forward one run"""
		self.ViewerOffset += 1
		self.show_run_data(self.Calendar)

	def show_run_data(self, calendar):
		"""Updates the right-hand viewer to the run passed"""
		selected = calendar.get_date()
		searchdate = date(selected[0], selected[1]+1, selected[2]) #(year, month+1, day)
		# Get a list of the runs on the selected day
		results = self.Log.runs_on(searchdate)
		# Test number of results we got
		if len(results) == 0:
			self.wTree.get_widget("table2").hide()
			self.wTree.get_widget("noruns").show()
			return 0 # nothing to see!
		# If there's more than one, be ready to deal with that with arrows
		elif len(results) > 1:
			self.wTree.get_widget("previous").show()
			self.wTree.get_widget("next").show()
			self.wTree.get_widget("noruns").hide()
			self.wTree.get_widget("table2").show()
		else:
			self.wTree.get_widget("previous").hide() # just in case they're being
			self.wTree.get_widget("next").hide() #  shown already
			self.wTree.get_widget("noruns").hide()
			self.wTree.get_widget("table2").show()
		if len(results) > 0:
			# Show Edit/Delete
			self.wTree.get_widget("hbox2").show()
			self.wTree.get_widget("edit").show()
			self.wTree.get_widget("delete").show()
		# Choose the run to display
		if self.ViewerOffset < 0:
			self.ViewerOffset = len(results)-1
		elif self.ViewerOffset > len(results)-1:
			self.ViewerOffset = 0
		# Actually show the stuff
		self.wTree.get_widget("table2").show()
		run = self.Log.get_run(searchdate, self.ViewerOffset)
		dist = self.Log.entry_distance(run)
		self.wTree.get_widget("datampw").set_text(str(self.Log.mpw(searchdate)))
		self.wTree.get_widget("datadate").set_text(date_string(searchdate))
		self.wTree.get_widget("datacourse").set_text(unescape(run.course))
		self.wTree.get_widget("datadistance").set_text(str(dist[0]) + " " + code_unit(dist[1]))
		self.wTree.get_widget("datatime").set_text(seconds_timestr(run.time))
		self.wTree.get_widget("datapace").set_text(calc_pace(seconds_timestr(run.time), run.distance, run.unit))
		shoestring = unescape(run.shoes) + " (" + str(self.Log.miles_on_shoe(run.shoes)) + " miles)"
		self.wTree.get_widget("datashoes").set_text(shoestring)
		self.wTree.get_widget("datanotes").set_text(unescape(run.notes))
		self.show_curve()

if __name__ == "__main__":
	gui = RitualGTK()

	if len(os.sys.argv) > 1: # always 1, for script name
		gui.open_file(os.sys.argv[1])

	gtk.main()
